import numpy as np
from sklearn import datasets
import matplotlib.pyplot as plt
from sklearn.datasets import load_digits
from sklearn.metrics import accuracy_score


class NN:
    def __init__(self, layers):
        self.layers = np.array(layers)  # 输入的神经网络构造
        self.n_layers = np.size(self.layers)  # 神经网络的层数
        self.n_input = self.layers[0]  # 输入个数
        self.n_output = self.layers[-1]  # 输出个数
        self.n_notes = np.sum(self.layers)  # 节点个数

    def sigmoid(self, f):
        g = 1.0 / (1.0 + np.exp(-f))  # 激活函数
        return g

    def initialization(self):  # 初始化
        W = []  # 权值
        B = []  # 偏置值
        Y = []  # 输出值
        D = []  # 误差值
        YY = []
        for i in range(self.n_layers - 1):
            w = np.random.randn(self.layers[i], self.layers[i + 1])  # 初始化权值
            b = np.random.randn(1, self.layers[i + 1])  # 初始化偏置值
            d = np.zeros(self.layers[i + 1])  # 初始化误差值
            W.append(w)
            B.append(b)
            D.append(d)
        for i in range(self.n_layers):
            y = np.zeros(self.layers[i])
            Y.append(y)
            YY.append(y)
        self.W = W
        self.B = B
        self.Y = Y
        self.D = D
        self.YY = YY

    def forward(self, X):
        self.Y[0] = X  # 初始输入值
        for i in range(self.n_layers - 1):
            self.Y[i + 1] = self.sigmoid(np.dot(self.Y[i], self.W[i]) + self.B[i])  # 层输出值

    def backward(self, X, Z):
        self.Z = Z  # 样本的标签值
        self.forward(X)  # 正向计算
        for i in range(len(self.D) - 1, -1, -1):
            if i == self.n_layers - 2:
                # 输出层节点误差
                self.D[i] = self.Y[i + 1] * (1 - self.Y[i + 1]) * (self.Z - self.Y[i + 1])
            else:
                # 隐藏层节点误差
                self.D[i] = self.Y[i + 1] * (1 - self.Y[i + 1]) * np.dot(self.D[i + 1], self.W[i + 1].T)

        # 权值更新
        for i in range(self.n_layers - 2, -1, -1):
            self.W[i] += self.epsilon * np.dot(self.Y[i].T, self.D[i])  # 权值矩阵更新
            self.B[i] += self.epsilon * np.sum(self.D[i], axis=0)  # 偏置值向量更新

    def training(self, X, Z, iterative, epsilon):
        self.initialization()
        self.epsilon = epsilon  # 输入的学习率
        for it in range(iterative):
            self.backward(X, Z)
            if it % 100 == 0:
                L = np.sum((self.Y[-1] - self.Z) ** 2)
                y_pred = np.argmax(self.Y[-1], axis=1)
                y_true = np.argmax(self.Z, axis=1)
                acc = accuracy_score(y_true, y_pred)
                print("L=", L, "acc=", acc)
        # print(self.W)
        # print(self.B)
        # print(self.Y)
        # print(self.D)

    def calculate(self, X):
        self.forward(X)
        n = np.shape(self.Y[-1])[0]
        y = np.zeros(n)
        for i in range(n):
            y[i] = np.argmax(self.Y[-1][i])
        return y


iterative = 2000  # 训练次数
test = 200  # 测试次数

# moons
np.random.seed(0)
moons_X, moons_y = datasets.make_moons(400, noise=0.20)
moons_Y = np.zeros((np.size(moons_y), 2))
for i in range(np.size(moons_y)):
    moons_Y[i, int(moons_y[i])] = 1
moons_test_X = moons_X[0:test]
moons_test_y = moons_y[0:test]

moons_nn = NN([2, 8, 8, 2])
moons_nn.initialization()
epsilon = 0.01
moons_nn.training(moons_X, moons_Y, iterative, epsilon)
moons_test_z = moons_nn.calculate(moons_test_X)
t = 0
for i in range(test):
    if moons_test_y[i] != moons_test_z[i]:
        t = t + 1
print("moons test acc", (test - t) / test)

# circles
circles = np.loadtxt(open("dataset_circles.csv", "rb"), delimiter=",", skiprows=0)
np.random.shuffle(circles)
circles_X = circles[:, 0:2]
circles_y = circles[:, 2]
circles_Y = np.zeros((np.size(circles_y), 2))
for i in range(np.size(circles_y)):
    circles_Y[i, int(circles_y[i])] = 1
circles_test_X = circles_X[0:test]
circles_test_y = circles_y[0:test]

circles_nn = NN([2, 8, 7, 2])
circles_nn.initialization()
epsilon = 0.01
circles_nn.training(circles_X, circles_Y, iterative, epsilon)
circles_test_z = circles_nn.calculate(circles_test_X)
t = 0
for i in range(test):
    if circles_test_y[i] != circles_test_z[i]:
        t = t + 1
print("circles test acc", (test - t) / test)

# digits
digits = load_digits()
digits_X = digits.data
digits_y = digits.target
digits_Y = np.zeros((np.size(digits_y), 10))
for i in range(np.size(digits_y)):
    digits_Y[i, int(digits_y[i])] = 1
digits_test_X = digits_X[0:test]
digits_test_y = digits_y[0:test]

digits_nn = NN([64, 16, 10])
digits_nn.initialization()
epsilon = 0.001
digits_nn.training(digits_X, digits_Y, iterative, epsilon)
digits_test_z = digits_nn.calculate(digits_test_X)
t = 0
for i in range(test):
    if digits_test_y[i] != digits_test_z[i]:
        t = t + 1
print("digits test acc", (test - t) / test)
